﻿using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using HK.Core.Common.Data;
using HK.Core.Common.Protocol;
using HK.Core.Common.Protocol.Data;
using HK.Core.Encryption.Protocol;
using UnityEngine;
using HLogger = HK.Core.Logs.Logger;

namespace HK.Core.Utils
{
    /// <summary>
    /// 工具类 : Token.
    /// </summary>
    public static class UtilsToken
    {
        /// <summary>
        /// 验证状态
        /// </summary>
        public enum VerifyStatus
        {
            /// <summary>
            /// Ok
            /// </summary>
            Ok = 0,
            
            /// <summary>
            /// Token无效
            /// </summary>
            Invalid = 1,
            
            /// <summary>
            /// Token认证失败
            /// </summary>
            Failed = 2,
            
            /// <summary>
            /// 算法不匹配
            /// </summary>
            AlgorithmsNotMatch = 3,
            
            /// <summary>
            /// 密钥不匹配
            /// </summary>
            SecretKeyNotMatch = 4
        }
        
        /// <summary>
        /// Token验证异常
        /// </summary>
        private class UtilsTokenVerifyException : Exception
        {
            public UtilsTokenVerifyException(string message) : base(message) {}
        }
        
        /// <summary>
        /// Token负载
        /// </summary>
        [Serializable]
        private sealed class TokenPayload : JsonData<TokenPayload>
        {

#region Create

            /// <summary>
            /// 创建Token负载
            /// </summary>
            /// <param name="iSecretKey">密钥</param>
            /// <param name="iAlgorithms">算法</param>
            /// <param name="iTag">Tag</param>
            /// <returns>Token负载</returns>
            public static TokenPayload Create(string iSecretKey, TokenAlgorithms iAlgorithms = TokenAlgorithms.Hs256, string iTag = null)
            {
                return new TokenPayload
                {
                    secretKey = iSecretKey,
                    algorithms = iAlgorithms, tag = iTag
                };
            }

#endregion

            /// <summary>
            /// 保护构造函数
            /// </summary>
            private TokenPayload()
            {
                createTime = UtilsTime.GetCurDateTimeTicks();
            }

            [SerializeField]
            private string secretKey = null;
            /// <summary>
            /// 密钥
            /// </summary>
            public string SecretKey => secretKey;
            
            [SerializeField]
            private TokenAlgorithms algorithms = TokenAlgorithms.Hs256;
            
            /// <summary>
            /// 算法
            /// </summary>
            public TokenAlgorithms Algorithms => algorithms;

            [SerializeField]
            private long createTime = 0L;
            /// <summary>
            /// 创建时间Ticks
            /// </summary>
            public long CreateTimeTicks => createTime;

            [SerializeField]
            private string tag = null;
            /// <summary>
            /// Tag
            /// </summary>
            public string Tag => tag;

            /// <summary>
            /// 有效标志位
            /// </summary>
            public override bool Valid => 0 < createTime;

            /// <summary>
            /// 应用数据
            /// </summary>
            /// <param name="iData">数据</param>
            /// <param name="iImport">导入标志位(true:从相应的*.json文件导入; false:反之;)</param>
            /// <returns>true:有变更; false:无变更;</returns>
            public override bool ApplyData(IJsonData iData, bool iImport = false)
            {
                if (null == iData || !iData.Valid) return false;

                var dirty = base.ApplyData(iData, iImport);
                if (dirty && !iImport)
                {
                    Dirty = true;
                }

                if (iData is TokenPayload payload)
                {
                    secretKey = UpdateValue(secretKey, payload.SecretKey, iImport);
                    algorithms = UpdateValue(algorithms, payload.Algorithms, iImport);
                    createTime = UpdateValue(createTime, payload.CreateTimeTicks, iImport);
                    tag = UpdateValue(tag, payload.Tag, iImport);
                }

                return Dirty;
            }
            
            /// <summary>
            /// 更新值
            /// </summary>
            /// <param name="iCurValue">当前值</param>
            /// <param name="iDstValue">目标值</param>
            /// <param name="iImport">导入标志位</param>
            /// <returns>可以更新的值</returns>
            private TokenAlgorithms UpdateValue(TokenAlgorithms iCurValue, TokenAlgorithms iDstValue, bool iImport)
            {
                var dirty = iCurValue != iDstValue;
                if (dirty && !iImport)
                {
                    Dirty = true;
                }

                return dirty ? iDstValue : iCurValue;
            }
            
            /// <summary>
            /// 清空
            /// </summary>
            public override void Clear()
            {
                base.Clear();

                secretKey = null;
                algorithms = TokenAlgorithms.Hs256;
                createTime = 0L;
                tag = null;
            }
        }
        
        /// <summary>
        /// 算法列表
        /// </summary>
        private static readonly IDictionary<TokenAlgorithms, Func<byte[], byte[], byte[]>> HashAlgorithms;
        private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
        
        /// <summary>
        /// 默认密钥
        /// </summary>
        private static readonly string DefaultSecretKey = "GQDstcKsx0NHjPOuXOYg5MbeJ1XT0uFiwDVvVBrk";

        /// <summary>
        /// 默认分隔符
        /// </summary>
        private static readonly char TokenSeparator = '.';
        
        static UtilsToken()
        {
            HashAlgorithms = new Dictionary<TokenAlgorithms, Func<byte[], byte[], byte[]>>
            {
                { TokenAlgorithms.Hs256, (key, value) =>
                {
                    byte[] hashBytes = null;
                    using (var sha = new HMACSHA256(key))
                    {
                        hashBytes = sha.ComputeHash(value);
                    }
                    return hashBytes;
                } },
                { TokenAlgorithms.Hs384, (key, value) =>
                {
                    byte[] hashBytes = null;
                    using (var sha = new HMACSHA384(key))
                    {
                        hashBytes = sha.ComputeHash(value);
                    }
                    return hashBytes;
                } },
                { TokenAlgorithms.Hs512, (key, value) =>
                {
                    byte[] hashBytes = null;
                    using (var sha = new HMACSHA512(key))
                    {
                        hashBytes = sha.ComputeHash(value);
                    }
                    return hashBytes;
                } }
            };
        }
        
        /// <summary>
        /// 转换Token算法类型
        /// </summary>
        /// <param name="iAlgorithm">Token算法</param>
        /// <returns>Token算法类型</returns>
        /// <exception cref="UtilsTokenVerifyException"></exception>
        private static TokenAlgorithms ConvertHashAlgorithms(string iAlgorithm)
        {
            switch (iAlgorithm)
            {
                case "Hs256": return TokenAlgorithms.Hs256;
                case "Hs384": return TokenAlgorithms.Hs384;
                case "Hs512": return TokenAlgorithms.Hs512;
                default: throw new UtilsTokenVerifyException("Algorithm not supported.");
            }
        }
        
        /// <summary>
        /// 创建签名片段
        /// </summary>
        /// <param name="iValue">用以签名的值</param>
        /// <param name="iPayload">有效负载</param>
        /// <param name="iAlgorithms">Token算法</param>
        /// <returns>签名片段列表</returns>
        private static List<string> GenerateSegments(byte[] iValue, 
            byte[] iAlgorithms, byte[] iPayload)
        {
            return new List<string>
            {
                UtilsBase64.Base64Encode(iValue), UtilsBase64.Base64Encode(iAlgorithms),
                UtilsBase64.Base64Encode(iPayload)
            };
        }
        
        /// <summary>
        /// 链接签名用片段列表
        /// </summary>
        /// <param name="iSegments">签名片段</param>
        /// <returns>签名字符串</returns>
        private static string JoinSegments(List<string> iSegments)
        {
            if (null == iSegments || 0 >= iSegments.Count) return null;
            
            return string.Join($"{TokenSeparator}", iSegments.ToArray());
        }

        /// <summary>
        /// 生成Token
        /// </summary>
        /// <param name="iSegments">签名片段</param>
        /// <param name="iEncode">字节编码</param>
        /// <param name="iSecretKey">密钥</param>
        /// <param name="iAlgorithms">Token算法</param>
        /// <returns>签名字符串</returns>
        private static string GenerateToken(List<string> iSegments, Encoding iEncode, TokenAlgorithms iAlgorithms, string iSecretKey)
        {
            if (null == iSegments || 0 >= iSegments.Count || string.IsNullOrEmpty(iSecretKey)) return null;
            
            // 生成签名字节
            var stringToSign = JoinSegments(iSegments);
            var bytesToSign = iEncode.GetBytes(stringToSign);
            var signature = HashAlgorithms[iAlgorithms](iEncode.GetBytes(iSecretKey), bytesToSign);
            // 追加签名
            iSegments.Add(UtilsBase64.Base64Encode(signature));

            return string.Join($"{TokenSeparator}", iSegments.ToArray());
        }

#region SecretKey

        /// <summary>
        /// 取得密钥
        /// </summary>
        /// <param name="iSecretKey">密钥</param>
        /// <returns>密钥</returns>
        private static string GetSecretKey(string iSecretKey) =>
            string.IsNullOrEmpty(iSecretKey) ? DefaultSecretKey : iSecretKey;
        
#endregion
        
#region Verify

        /// <summary>
        /// 验证
        /// </summary>
        /// <param name="iCurSignature">当前签名</param>
        /// <param name="iCalcSignature">计算所得签名</param>
        /// <param name="iPayload">有效负载</param>
        /// <returns>true:OK; false:NG;</returns>
        private static bool Verify(string iCurSignature, string iCalcSignature, TokenPayload  iPayload)
        {
            if (string.IsNullOrEmpty(iCurSignature) || string.IsNullOrEmpty(iCalcSignature) ||
                !iCurSignature.Equals(iCalcSignature) || null == iPayload || !iPayload.Valid) return false;

            var curTimeTicks = UtilsTime.GetCurDateTimeTicks();

            return curTimeTicks > iPayload.CreateTimeTicks;
        }

#endregion
        
#region Encode
        
        /// <summary>
        /// Token加密
        /// </summary>
        /// <param name="iValue">欲要生成Token的值</param>
        /// <param name="iEncode">字节编码</param>
        /// <param name="iSecretKey">密钥</param>
        /// <param name="iAlgorithm">Token加密算法(默认:Hs256)</param>
        /// <param name="iPayloadTag">有效负载Tag</param>
        /// <returns>Token</returns>
        public static string Encode(string iValue, Encoding iEncode, 
            string iSecretKey = null, TokenAlgorithms iAlgorithm = TokenAlgorithms.Hs256,
            string iPayloadTag = null)
        {
            if (string.IsNullOrEmpty(iValue)) return null;
            
            // 密钥
            var secretKey = GetSecretKey(iSecretKey);
            
            // 字节编码转换
            var bodyBytes = iEncode.GetBytes(iValue);
            
            // Token算法类型
            var algorithmBytes = iEncode.GetBytes($"{iAlgorithm}");
            
            // 有效负载
            var payload = TokenPayload.Create(secretKey, iAlgorithm, iPayloadTag);
            var payloadTxt = UtilsJson<TokenPayload>.ConvertToJsonString(payload, false);
            var payloadBytes = iEncode.GetBytes(payloadTxt);
            // 生成签名片段
            var segments = GenerateSegments(bodyBytes, algorithmBytes, payloadBytes);
            
            // 生成Token
            return GenerateToken(segments, iEncode, iAlgorithm, secretKey);
        }

        /// <summary>
        /// Token加密(UTF8)
        /// </summary>
        /// <param name="iValue">欲要生成Token的值</param>
        /// <param name="iSecretKey">密钥</param>
        /// <param name="iAlgorithm">Token加密算法(默认:Hs256)</param>
        /// <param name="iPayloadTag">有效负载Tag</param>
        /// <returns>Token</returns>
        public static string Encode(string iValue, string iSecretKey = null,
            TokenAlgorithms iAlgorithm = TokenAlgorithms.Hs256, string iPayloadTag = null)
        {
            return Encode(iValue, Encoding.UTF8, iSecretKey, iAlgorithm, iPayloadTag);
        }
        
        /// <summary>
        /// Token加密
        /// </summary>
        /// <param name="iJson">Json数据对象</param>
        /// <param name="iEncode">字节编码</param>
        /// <param name="iSecretKey">密钥</param>
        /// <param name="iAlgorithm">Token加密算法(默认:Hs256)</param>
        /// <typeparam name="TJson">Json数据类型</typeparam>
        /// <returns>Token</returns>
        public static string Encode<TJson>(TJson iJson, Encoding iEncode, string iSecretKey = null,
            TokenAlgorithms iAlgorithm = TokenAlgorithms.Hs256)
            where TJson : JsonData, IToken
        {

            if (null == iJson || !iJson.Valid) return null;
            var secretKey = GetSecretKey(iSecretKey);
            if (string.IsNullOrEmpty(secretKey)) return null;
            
            var jsonStr = UtilsJson<TJson>.ConvertToJsonString(iJson);
            if (string.IsNullOrEmpty(jsonStr)) return null;
            
            return Encode(jsonStr, iEncode, secretKey, iAlgorithm, iJson.PayloadTag);
        }
        
        /// <summary>
        /// Token加密(UTF8)
        /// </summary>
        /// <param name="iJson">Json数据对象</param>
        /// <param name="iSecretKey">密钥</param>
        /// <param name="iAlgorithm">Token加密算法(默认:Hs256)</param>
        /// <typeparam name="TJson">Json数据类型</typeparam>
        /// <returns>Token</returns>
        public static string Encode<TJson>(TJson iJson, string iSecretKey = null,
            TokenAlgorithms iAlgorithm = TokenAlgorithms.Hs256)
            where TJson : JsonData, IToken
        {
            return Encode(iJson, Encoding.UTF8, iSecretKey, iAlgorithm);
        }
        
#endregion

#region Decode

        /// <summary>
        /// 解密Token(UTF8)
        /// </summary>
        /// <param name="iToken">Token</param>
        /// <param name="oJson">解密后的Json对象</param>
        /// <param name="iSecretKey">密钥</param>
        /// <param name="iVerify">是否验证</param>
        /// <typeparam name="TJson">Json对象类型</typeparam>
        /// <returns>解密状态</returns>
        /// <exception cref="ArgumentException"></exception>
        public static VerifyStatus Decode<TJson>(string iToken, out TJson oJson,
            string iSecretKey = null, bool iVerify = true)
            where TJson : JsonData, IToken
        {
            return Decode<TJson>(iToken, Encoding.UTF8, out oJson, iSecretKey, iVerify);
        }

        /// <summary>
        /// 解密Token
        /// </summary>
        /// <param name="iToken">Token</param>
        /// <param name="oJson">解密后的Json对象</param>
        /// <param name="iEncode">字节编码</param>
        /// <param name="iSecretKey">密钥</param>
        /// <param name="iVerify">是否验证</param>
        /// <typeparam name="TJson">Json对象类型</typeparam>
        /// <returns>解密状态</returns>
        /// <exception cref="ArgumentException"></exception>
        public static VerifyStatus Decode<TJson>(string iToken, Encoding iEncode, 
            out TJson oJson, string iSecretKey = null, bool iVerify = true)
            where TJson : JsonData, IToken
        {
            oJson = default(TJson);
            var status = Decode(iToken, out string oDecodeStr, iEncode, iSecretKey, iVerify);
            if (VerifyStatus.Ok != status) return status;
            if (string.IsNullOrEmpty(oDecodeStr))
            {
                HLogger.Error("TokenPayload::Decode():Failed!");
                return VerifyStatus.Invalid;
            }
            
            // 解析Json数据
            oJson = UtilsJson<TJson>.ConvertFromJsonString(oDecodeStr);
            if (default(TJson) == oJson || !oJson.Valid)
            {
                HLogger.Error("TokenPayload::Decode():Failed!");
                oJson = default(TJson);
                return VerifyStatus.Failed;
            }

            return status;
        }

        /// <summary>
        /// 解密Token(UTF8)
        /// </summary>
        /// <param name="iToken">Token</param>
        /// <param name="oDecodeStr">解密后的Json对象</param>
        /// <param name="iSecretKey">密钥</param>
        /// <param name="iVerify">是否验证</param>
        /// <returns>解密状态</returns>
        /// <exception cref="ArgumentException">参数异常</exception>
        public static VerifyStatus Decode(string iToken, out string oDecodeStr,
            string iSecretKey = null, bool iVerify = true)
        {
            return Decode(iToken, out oDecodeStr, Encoding.UTF8, iSecretKey, iVerify);
        }
        
        /// <summary>
        /// 解密Token
        /// </summary>
        /// <param name="iToken">Token</param>
        /// <param name="iSecretKey">密钥</param>
        /// <param name="iVerify">是否验证</param>
        /// <returns>解密状态</returns>
        /// <exception cref="ArgumentException">参数异常</exception>
        public static VerifyStatus Decode(string iToken, string iSecretKey = null, bool iVerify = true)
        {
            return Decode(iToken, Encoding.UTF8, iSecretKey, iVerify);
        }

        /// <summary>
        /// 解密Token
        /// </summary>
        /// <param name="iToken">Token</param>
        /// <param name="iEncode">字节编码</param>
        /// <param name="iSecretKey">密钥</param>
        /// <param name="iVerify">是否验证</param>
        /// <returns>解密状态</returns>
        /// <exception cref="ArgumentException">参数异常</exception>
        public static VerifyStatus Decode(string iToken, Encoding iEncode, 
            string iSecretKey = null, bool iVerify = true)
        {
            return Decode(iToken, out var oDecodeStr, iEncode, iSecretKey, iVerify);
        }
        
        /// <summary>
        /// 解密Token
        /// </summary>
        /// <param name="iToken">Token</param>
        /// <param name="oDecodeStr">解密后的Json对象</param>
        /// <param name="iEncode">字节编码</param>
        /// <param name="iSecretKey">密钥</param>
        /// <param name="iVerify">是否验证</param>
        /// <returns>解密状态</returns>
        /// <exception cref="ArgumentException">参数异常</exception>
        public static VerifyStatus Decode(string iToken, out string oDecodeStr, 
            Encoding iEncode, string iSecretKey = null, bool iVerify = true)
        {
            oDecodeStr = null;
            if (string.IsNullOrEmpty(iToken))
            {
                HLogger.Error("TokenPayload::Decode():The token is null or empty!");
                return VerifyStatus.Invalid;
            }

            // 密钥
            var secretKey = GetSecretKey(iSecretKey);
            
            var parts = iToken.Split(TokenSeparator);
            if (parts.Length != 4)
            {
                throw new ArgumentException("Invalid Token!(ErrorCode:1)");
            }
            
            // 算法类型
            var algorithmTxt64 = parts[1];
            var algorithmTxt = iEncode.GetString(UtilsBase64.Base64Decode(algorithmTxt64));
            var algorithms = ConvertHashAlgorithms(algorithmTxt);
            
            // 负载
            var payload64 = parts[2];
            var payloadJsonTxt = iEncode.GetString(UtilsBase64.Base64Decode(parts[2]));
            var payload = UtilsJson<TokenPayload>.ConvertFromJsonString(payloadJsonTxt);
            // 算法不一致
            if (algorithms != payload.Algorithms)
            {
                HLogger.Error($"TokenPayload::Decode():The algorithms of token is matched!({algorithms}-{payload.Algorithms})");
                return VerifyStatus.AlgorithmsNotMatch;
            }
            
            // 密钥不匹配
            if (UtilsString.Diff(secretKey, payload.SecretKey))
            {
                HLogger.Error("TokenPayload::Decode():SecretKey Not Match!");
                return VerifyStatus.SecretKeyNotMatch;
            }
            
            var value64 = parts[0];
            oDecodeStr = iEncode.GetString(UtilsBase64.Base64Decode(value64));
            
            var curSignature = UtilsBase64.Base64Decode(parts[3]);

            // 验证
            if (iVerify)
            {
                var singTxt64 = string.Concat(value64, $"{TokenSeparator}", algorithmTxt64, $"{TokenSeparator}", payload64);
                var bytesToSign = iEncode.GetBytes(singTxt64);

                // 当前签名
                var decodedCurSignature = Convert.ToBase64String(curSignature);
                
                // 计算签名
                var secretBytes = iEncode.GetBytes(secretKey);
                var calcSignature = HashAlgorithms[algorithms](secretBytes, bytesToSign);
                var decodedCalcSignature = Convert.ToBase64String(calcSignature);

                // 认证
                if (!Verify(decodedCurSignature, decodedCalcSignature, payload))
                {
                    oDecodeStr = null;
                    return VerifyStatus.Failed;
                }
            }

            return VerifyStatus.Ok;
        }

#endregion

    }
}